#TODO: in backend.py translate cstr to ccode and execute pystr
#TODO: in interpret.py running dslc code

import sys
from .core.dslc_parser import ASTNode, UnaryOp, BinaryOp, BlockOp, Identifier, Number, String
from .core.dslc_symbol_table import SymbolTable
from .core.dslc_misc import SafeDict

def evaluation(stab: SymbolTable, ast: ASTNode):
    '''执行代码，更新所有能计算的symbol'''
    ast = __evaluation(stab, ast)
    return ast


def __evaluation(stab: SymbolTable, ast: ASTNode,\
    keep_id: bool=False, in_loop_block=False):
    '''执行代码'''
    #跳过macro定义的block中的执行
    for e in stab.scopes:
        if e.startswith('macro'):
            return ast
    if isinstance(ast, Identifier):
        if in_loop_block:
            return ast
        try:
            ast.name = ast.name.format_map(stab.lsvisiable())
        except:
            print("Identifier evaluation corrupt {}".format(ast))
        if keep_id:
            return ast
        return __id_evaluation(stab, ast)
    if isinstance(ast, UnaryOp):
        ast.x = __evaluation(stab, ast.x, in_loop_block=in_loop_block)
    if isinstance(ast, BinaryOp):
        print('ast--->', ast)
        ast.l = __evaluation(stab, ast.l, keep_id=True, in_loop_block=in_loop_block)
        ast.r = __evaluation(stab, ast.r, in_loop_block=in_loop_block)
        ##在for循环中的block不需要做赋值操作，在展开以后再eval。处理掉Binary中的l和r以后就可以回去了
        if in_loop_block:
            return ast
        #if left is const，const_def_pass will change it to
        #Number or String，so do not need to lookup whether is defined
        if ast.op == "=" and isinstance(ast.l, Identifier):
            #如果等号右侧是一个BlockOp，跳过这个赋值
            #TODO: 区分pre_evaluation和post_evaluation，post_evaluation需要从block中取值
            if isinstance(ast.r, BlockOp):
                #if len(ast.r.oplist) == 1:
                #    if isinstance(ast.r.oplist[0], Number):
                #        newvalue = int(ast.r.oplist[0].value)
                #    elif isinstance(ast.r.oplist[0], String):
                #        newvalue = ast.r.oplist[0].value
                #    isym = stab.lookup(ast.l.name)
                #    if isym:
                #        isym.info['astobj'] = ast.r
                #    else:
                #        stab.add(ast.l.name, {'astobj': ast.r})
                #else:
                isym = stab.lookup(ast.l.name)
                if isym:
                    isym.info['astobj'] = BlockOp([e.copy() for e in ast.r.oplist])
                else:
                    stab.add(ast.l.name, {'astobj': ast.r.copy()})
                return ast
            elif isinstance(ast.r, Number):
                newvalue = int(ast.r.value)
            elif isinstance(ast.r, String):
                newvalue = ast.r.value
            elif isinstance(ast.r, Identifier):
                isym = stab.lookup(ast.r.name)
                if not isym:
                    print('= assign corrupt {}'.format(ast))
                    return ast
                if 'ival' in isym.info:
                    newvalue = isym['ival']
                elif 'astobj' in isym.info:
                    isym2 = stab.lookup(ast.l.name)
                    if isym2 is not None:
                        isym2.info['astobj'] = isym.info['astobj'].copy()
                    else:
                        stab.add(ast.l.name, {'astobj': isym.info['astobj'].copy()})
                    return ast
                else:
                    raise
            else:
                #print(ast)
                #if(ast.l.name == 'slidx'):
                #    print(ast)
                #    raise
                return ast
            isym = stab.lookup(ast.l.name)
            if isym:
                isym.info['ival'] = newvalue
            else:
                stab.add(ast.l.name, {'ival': newvalue})
        if ast.op in ['<<', '+', '*', '>>', '|']:
            print(ast, ast.parent)
            print(stab.scopes)
            if isinstance(ast.l, Identifier):
                lval = stab.lookup(ast.l.name).info['ival']
            else:
                lval = ast.l.value
            if isinstance(ast.r, Identifier):
                rval = stab.lookup(ast.r.name).info['ival']
            else:
                rval = ast.r.value
            _code_to_run = '{} {} {}'.format(lval, ast.op, rval)
            #print(_code_to_run)
            ret = Number(str(eval(_code_to_run)))
            ret.parent = ast.parent
            return ret
        if ast.op == "$":
            isym = stab.lookup(ast.l.name)
            if isinstance(ast.r, Identifier):
                rval = stab.lookup(ast.r.name).info['ival']
                #print(rval)
                newvalue = isym.info["astobj"].oplist[int(rval)].value
            else:
                newvalue = isym.info["astobj"].oplist[int(ast.r.value)].value
            if isinstance(newvalue, str):
                return String(newvalue)
            else:
                return Number(newvalue)
            #print(ast)
    #if isinstance(ast, String):
    #    #if isinstance(ast.parent, BlockOp):
    #    param = stab.lsvisiable()
    #    param = SafeDict(param)
    #    print(ast.value)
    #    ret = String(ast.value.format_map(param))
    #    ret.parent = ast.parent
    #    return ret
    if isinstance(ast, BlockOp):
        oplistn = []
        sdeepth = 0
        _in_loop_block = in_loop_block
        for it in ast.oplist:
            # 处理attr
            if isinstance(it, UnaryOp) and it.op == ':':
                if isinstance(it.x, Identifier):
                    stab.enter_scope(it.x.name)
                elif isinstance(it.x, BinaryOp):
                    stab.enter_scope(it.x.l.name+"::"+it.x.r.value)
                else:
                    raise ValueError('can not parse COLON UnaryOp')
                sdeepth = sdeepth + 1
            #判断block是不是for
            if isinstance(it, Identifier) and it.name == 'for':
                _in_loop_block = True
            # 内部的赋值
            oplistn.append(__evaluation(stab, it, in_loop_block=_in_loop_block))
        ast.oplist = oplistn
        #
        for _ in range(sdeepth):
            stab.exit_scope()
    #if isinstance(ast, String):
    #    print(ast, ast.parent)
    #    if ast.value.startswith("Ush_base0_slice"):
    #        print(ast)
    #        print('==========================')
    #    try:
    #        ast.value = ast.value.format_map(stab.lsvisiable())
    #    except:
    #        print("String eval corrupt {}".format(ast))
    #    if ast.value.startswith("Ush_base0_slice"):
    #        print(ast)
    #        print('==========================')
    return ast


def __id_evaluation(stab: SymbolTable, ast: Identifier):
    '''Identifier的执行'''
    #TODO: Identifier name也可以演算
    istr = ast.name
    isym = stab.lookup(istr)
    if not isym:
        return ast
    if 'ival' in isym.info:
        if isinstance(isym.info['ival'], str):
            ret = String(isym.info['ival'])
        else:
            ret = Number(isym.info['ival'])
        ret.parent = ast.parent
        return ret
    return ast
